package com.itextpdf.local.ess.Signature;

import com.itextpdf.local.ess.PDFLocation.PDELocation;
import com.itextpdf.local.ess.predefine.SealStruct;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSigLockDictionary;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.security.*;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.util.UUID;

import static com.itextpdf.local.ess.Signature.ESSEmptyContainer.*;

public class ESSMakeSignature{

    public static byte[] makeSignature(byte[] pdfByte,PDELocation pdeLocation,SealStruct sealDto) throws Exception{
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        byte[] result;

        PdfReader reader =null;

        PdfStamper stamper =null;

        String signSerialNum = sealDto.getSealId();

        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        try{
            //读取源文件
            reader = new PdfReader(pdfByte);
            stamper = PdfStamper
                    .createSignature(reader, os, '\u0000', null, true);//注意此处的true 允许多次盖章，false则只能盖一个章。

            //设置签名的外观显示
            PdfSignatureAppearance appearance = stamper.getSignatureAppearance();

            //规定签章的权限，包括三种，详见itext 5 api，  NO_CHANGES_ALLOWED   FORM_FILLING_AND_ANNOTATION
            PdfSigLockDictionary dictionary = new PdfSigLockDictionary(
                    PdfSigLockDictionary.LockPermissions.FORM_FILLING_AND_ANNOTATION);

            appearance.setFieldLockDict(dictionary);
            //设置签名原因
            appearance.setReason(signSerialNum);
            //指定签章时间
//            appearance.setSignDate();
            //设置透明度 权值混合
//            pic.setSmask(true);
            //初始化签名的图片信息
            Image pic = Image.getInstance(sealDto.getImgByte());
            appearance.setImage(pic);

            float x = pdeLocation.getX();
            float y = pdeLocation.getY();
            float width = sealDto.getWidth();
            float height = sealDto.getHeight();
            int pageNum = pdeLocation.getPageNum();
            //中心点
            appearance.setVisibleSignature(
                    new Rectangle(x-width/2, y-height/2, x + width/2, y + height/2), pageNum,
                    "ESS" + signSerialNum);//fileName 一个文档中不能有重名的filedname  普通定位

            appearance.setLayer2Text("");//设置文字为空否则签章上将会有文字 影响外观

            //摘要算法
            ExternalDigest digest = new BouncyCastleDigest();
            byte[] pfxFile = sealDto.getPfxByte();
            String pwd = sealDto.getPfxPsw();
            KeyStore ks = null;
            ks = KeyStore.getInstance("PKCS12","BC");
            ks.load(new ByteArrayInputStream(pfxFile), pwd.toCharArray());
            String alias = (String) ks.aliases().nextElement();
            PrivateKey pk = (PrivateKey) ks.getKey(alias, pwd.toCharArray());
            //公钥证书
            Certificate[] chain = ks.getCertificateChain(alias);
            //签名算法
            ExternalSignature es = new PrivateKeySignature(pk, getHashAlgorithm(chain), "BC");

            MakeSignature.signDetached(appearance, digest, es, chain, null, null, null, 0,
                    MakeSignature.CryptoStandard.CMS);
        }finally {
            if (stamper != null) {
                stamper.close();
            }
            if (reader != null) {
                reader.close();
            }
            //输出签章结果
            result = os.toByteArray();
            os.close();
        }
        return result;
    }

    public static PreSignatureBody preMakeSignature(byte[] pdfByte, PDELocation pdeLocation, SealStruct sealDto) throws IOException, DocumentException, GeneralSecurityException {

        ByteArrayOutputStream os = new ByteArrayOutputStream();

        PreSignatureBody result = null;

        PdfReader reader =null;

        PdfStamper stamper =null;

        String signSerialNum = sealDto.getSealId();

        String fieldName = "ESS"+UUID.randomUUID().toString();

        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        try {
            //读取源文件
            reader = new PdfReader(pdfByte);
            stamper = PdfStamper
                    .createSignature(reader, os, '\u0000', null, true);//注意此处的true 允许多次盖章，false则只能盖一个章。

            //设置签名的外观显示
            PdfSignatureAppearance appearance = stamper.getSignatureAppearance();

            //规定签章的权限，包括三种，详见itext 5 api，  NO_CHANGES_ALLOWED   FORM_FILLING_AND_ANNOTATION
            PdfSigLockDictionary dictionary = new PdfSigLockDictionary(
                    PdfSigLockDictionary.LockPermissions.FORM_FILLING_AND_ANNOTATION);

            appearance.setFieldLockDict(dictionary);
            //设置签名原因
            appearance.setReason(signSerialNum);
            //指定签章时间
//            appearance.setSignDate();
            //设置透明度 权值混合
//            pic.setSmask(true);
            //初始化签名的图片信息
            Image pic = Image.getInstance(sealDto.getImgByte());
            appearance.setImage(pic);

            float x = pdeLocation.getX();
            float y = pdeLocation.getY();
            float width = sealDto.getWidth();
            float height = sealDto.getHeight();
            int pageNum = pdeLocation.getPageNum();
            //中心点
            appearance.setVisibleSignature(
                    new Rectangle(x - width / 2, y - height / 2, x + width / 2, y + height / 2), pageNum,
                    fieldName);//fileName 一个文档中不能有重名的filedname  普通定位

            appearance.setLayer2Text("");//设置文字为空否则签章上将会有文字 影响外观

            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            ByteArrayInputStream userCertificate = new ByteArrayInputStream(sealDto.getCertByte());
            CertificateFactory cf = CertificateFactory.getInstance("X.509","BC");
            Certificate[] chain = new Certificate[]{cf.generateCertificate(userCertificate)};

            ExternalSignatureContainer external = new ESSEmptyContainer.EmptyContainer();

            external.setHashAlgorithm(getHashAlgorithm(chain));

            MakeSignature.signExternalContainer(appearance, external, 8192);

            byte[] hash = external.getHash();
            Certificate[] certs = {null};
            PdfPKCS7 sgn = getPkcs(certs);
            byte[] toSign = sgn.getAuthenticatedAttributeBytes(hash, getOscp(chain), null,
                    MakeSignature.CryptoStandard.CMS);

            result = new PreSignatureBody(fieldName,hash,toSign,pdfByte);
        }finally {
            if (stamper != null) {
                stamper.close();
            }
            if (reader != null) {
                reader.close();
            }
            os.close();
        }
        return result;
    }

    public static byte[] SignPreSignedPdf(byte[] pdfByte, SealStruct sealDto, PreSignatureBody preSignatureBody)  throws IOException, DocumentException, GeneralSecurityException{
        byte[] result = null;
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        ByteArrayInputStream userCertificate = new ByteArrayInputStream(sealDto.getCertByte());
        CertificateFactory cf = CertificateFactory.getInstance("X.509","BC");
        Certificate[] chain = new Certificate[]{cf.generateCertificate(userCertificate)};

        PdfPKCS7 sgn = getPkcs(chain);

        sgn.setExternalDigest(preSignatureBody.getSignatureBytes(), null, "SM2");

        byte[] encodedSig = sgn.getEncodedPKCS7(preSignatureBody.getHashBytes(), getTsa(), getOscp(chain), null,
                MakeSignature.CryptoStandard.CMS);

        PdfReader reader = new PdfReader(pdfByte);

        ExternalSignatureContainer external = new ESSEmptyContainer.MyExternalSignatureContainer(encodedSig);

        ByteArrayOutputStream os = new ByteArrayOutputStream();

        MakeSignature.signDeferred(reader, preSignatureBody.getFieldName(), os, external);

        result = os.toByteArray();

        reader.close();

        os.close();
        return result;
    }
}
